//
// Created by antler on 2018/12/5.
//

#include "lprefix.h"

#include <string>
#include <sstream>

#include "lua.hpp"
#include "luaext.hpp"

#include "log.h"
#include "GlobalMacros.h"

#include "Screen.h"

static int screen_tostring(lua_State *L) {
    lua_pushfstring(L, "class: %p", lua_topointer(L, 1));
    return 1;
}

static int screen_init(lua_State *L) {
    int ori = luaL_checkint(L, 1);
    if (ori == Screen::LANDSCAPE_RIGHT ||
        ori == Screen::LANDSCAPE_LEFT ||
        ori == Screen::PORTRAIT) {
        Screen::getInstance()->init(ori);
    } else {
        tolua_Error tolua_err = {1, 0, "screen.LANDSCAPE"};
        tolua_error(L, "#fscreen.init", &tolua_err);
    }
    return 0;
}

static int screen_getSize(lua_State *L) {
    Size *size = Size::createWith(Screen::getInstance()->getSize());
    tolua_pushusertype_and_takeownership(L, size, USERTYPE_SIZE);
    return 1;
}

static int screen_getDPI(lua_State *L) {
    lua_pushinteger(L, Screen::getInstance()->getDPI());
    return 1;
}

static int screen_capture(lua_State *L) {
    Image *image = nullptr;

    int argc = lua_gettop(L);
    if (argc == 0) {
        image = Screen::getInstance()->capture();
    } else if (argc == 1) {
        Rect *rect = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, LUA_TNIL);
        if (rect) {
            image = Screen::getInstance()->capture(*rect);
        }
    }

    if (image) {
        tolua_pushusertype_and_takeownership(L, image, USERTYPE_IMAGE);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int screen_keep(lua_State *L) {
    tolua_Error tolua_err;
    if (tolua_isboolean(L, 1, 0, &tolua_err)) {
        bool value = lua_toboolean(L, 1) ? true : false;
        Screen::getInstance()->keep(value);
    } else {
        tolua_error(L, "#fscreen.keep", &tolua_err);
    }
    return 0;
}

static int screen_getOrientation(lua_State *L) {
    lua_pushinteger(L, Screen::getInstance()->getOrientation());
    return 1;
}

static int screen_snapshot(lua_State *L) {
    const char *filePath = luaL_checkstring(L, 1);
    Rect *rect = (Rect *) toluahelper_tousertype(L, 2, USERTYPE_RECT, &Rect::ZERO);
    int quality = luaL_optint(L, 3, QUALITY_DEFAULT);

    bool ret = Screen::getInstance()->snapshot(filePath, *rect, quality);
    lua_pushboolean(L, ret ? 1 : 0);
    return 1;
}

static int screen_getRGB(lua_State *L) {
    Color3B c3b;

    int argc = lua_gettop(L);
    if (argc == 1) {
        tolua_Error tolua_err;
        Point *point = (Point *) toluahelper_tousertype(L, 1, USERTYPE_POINT, nullptr, &tolua_err);
        if (point) {
            Screen::getInstance()->getColor(*point, c3b);
        } else {
            tolua_error(L, "#fscreen.getRGB", &tolua_err);
        }
    } else if (argc == 2) {
        int x = luaL_checkint(L, 1);
        int y = luaL_checkint(L, 2);

        Screen::getInstance()->getColor(Point(x, y), c3b);
    }

    lua_pushinteger(L, c3b.r);
    lua_pushinteger(L, c3b.g);
    lua_pushinteger(L, c3b.b);
    return 3;
}

static int screen_getColor(lua_State *L) {
    Color3B c3b;

    int argc = lua_gettop(L);
    if (argc == 1) {
        tolua_Error tolua_err;
        Point *point = (Point *) toluahelper_tousertype(L, 1, USERTYPE_POINT, nullptr, &tolua_err);
        if (point) {
            Screen::getInstance()->getColor(*point, c3b);
        } else {
            tolua_error(L, "#fscreen.getColor", &tolua_err);
        }
    } else if (argc == 2) {
        int x = luaL_checkint(L, 1);
        int y = luaL_checkint(L, 2);

        Screen::getInstance()->getColor(Point(x, y), c3b);
    }

    Color3B *ret = Color3B::createWith(c3b);
    tolua_pushusertype_and_takeownership(L, ret, USERTYPE_COLOR3B);
    return 1;
}

static int screen_matchColor(lua_State *L) {
    bool bMatchColor = false;

    char buf[128] = {};
    int fuzziness = FUZZINESS_DEFAULT;

    int type = lua_type(L, 1);
    if (type == LUA_TNUMBER) {
        int x = luaL_checkint(L, 1);
        int y = luaL_checkint(L, 2);

        type = lua_type(L, 3);
        if (type == LUA_TNUMBER) {
            int color = luaL_checkint(L, 3);
            snprintf(buf, sizeof(buf), "%d|%d|0x%06x", x, y, color);
            fuzziness = luaL_optint(L, 4, FUZZINESS_DEFAULT);
        } else if (type == LUA_TUSERDATA) {
            tolua_Error tolua_err;
            if (tolua_isusertype(L, 3, USERTYPE_COLOR3F, LUA_TNIL, &tolua_err)) {
                Color3F *c3f = (Color3F *) (tolua_tousertype(L, 3, nullptr));
                if (c3f) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", x, y, c3f->toInt());
                    fuzziness = luaL_optint(L, 4, FUZZINESS_DEFAULT);
                }
            } else if (tolua_isusertype(L, 3, USERTYPE_COLOR3B, LUA_TNIL, &tolua_err)) {
                Color3B *c3b = (Color3B *) (tolua_tousertype(L, 3, nullptr));
                if (c3b) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", x, y, c3b->toInt());
                    fuzziness = luaL_optint(L, 4, FUZZINESS_DEFAULT);
                }
            } else {
                tolua_error(L, "#fscreen.matchColor", &tolua_err);
            }
        }
    } else if (type == LUA_TUSERDATA) {
        tolua_Error tolua_err;
        Point *point = (Point *) toluahelper_tousertype(L, 1, USERTYPE_POINT, nullptr, &tolua_err);
        if (point) {
            type = lua_type(L, 2);
            if (type == LUA_TNUMBER) {
                int color = luaL_checkint(L, 2);
                snprintf(buf, sizeof(buf), "%d|%d|0x%06x", point->x, point->y, color);
                fuzziness = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            } else if (type == LUA_TUSERDATA) {
                tolua_Error tolua_err;
                if (tolua_isusertype(L, 2, USERTYPE_COLOR3F, LUA_TNIL, &tolua_err)) {
                    Color3F *c3f = (Color3F *) (tolua_tousertype(L, 2, nullptr));
                    if (c3f) {
                        snprintf(buf, sizeof(buf), "%d|%d|0x%06x", point->x, point->y,
                                 c3f->toInt());
                        fuzziness = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    }
                } else if (tolua_isusertype(L, 2, USERTYPE_COLOR3B, LUA_TNIL, &tolua_err)) {
                    Color3B *c3b = (Color3B *) (tolua_tousertype(L, 2, nullptr));
                    if (c3b) {
                        snprintf(buf, sizeof(buf), "%d|%d|0x%06x", point->x, point->y,
                                 c3b->toInt());
                        fuzziness = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    }
                } else {
                    tolua_error(L, "#fscreen.matchColor", &tolua_err);
                }
            }
        } else {
            tolua_error(L, "#fscreen.matchColor", &tolua_err);
        }
    } else {
        tolua_Error tolua_err = {1, 0, "(x, y) or Point"};
        tolua_error(L, "#fscreen.matchColor", &tolua_err);
    }

    if (strlen(buf)) {
        bMatchColor = Screen::getInstance()->matchColors(buf, fuzziness);
    }

    lua_pushboolean(L, bMatchColor ? 1 : 0);
    return 1;
}

static std::string tolua_colorTable_to_color(lua_State *L, int idx) {
    std::ostringstream color;

    std::string pos;
    std::string cr;
    std::string offset;
    std::string fuzz;

    lua_pushnil(L); // Stack: table, nill
    while (lua_next(L, idx)) { // Stack: table, k, v
        const std::string key = lua_tostring(L, -2);
        if (key == "pos") {
            Point *point = (Point *) toluahelper_tousertype(L, -1, USERTYPE_POINT, nullptr);
            if (point) {
                char buff[128] = {};
                snprintf(buff, sizeof(buff), "%d|%d", point->x, point->y);
                pos = buff;
            }
        } else if (key == "color") {
            int value = luaL_checkint(L, -1);
            char buff[16] = {};
            snprintf(buff, sizeof(buff), "0x%06x", value);
            cr = buff;
        } else if (key == "offset") {
            int value = luaL_checkint(L, -1);
            char buff[16] = {};
            snprintf(buff, sizeof(buff), "0x%06x", value);
            offset = buff;
        } else if (key == "fuzz") {
            int value = luaL_checkint(L, -1);
            char buff[10] = {};
            snprintf(buff, sizeof(buff), "%d", value);
            fuzz = buff;
        } else {
            luaL_error(L, "color table not support key:%s", key.c_str());
        }

        lua_pop(L, 1); // Stack: table, k
    }

    if (pos.empty() || cr.empty()) {
        luaL_error(L, "need key 'pos' or 'color'");
    }

    color << pos;
    color << "|" << cr;
    if (!offset.empty()) {
        color << "-" << offset;
    }

    if (!fuzz.empty()) {
        color << "|" << fuzz;
    }

    return color.str();
}

static std::string tolua_colorTable_to_colors(lua_State *L, int idx) {
    std::string colors;

    int i = 1;
    lua_pushnil(L); // Stack: table, nill
    while (lua_next(L, idx)) { // Stack: table, k, v
        int type = lua_type(L, -1);
        if (type == LUA_TTABLE) {
            int top = lua_gettop(L);
            std::string color = tolua_colorTable_to_color(L, top);
            if (!color.empty()) {
                if (i != 1) {
                    colors.append(",");
                }
                colors.append(color);
                ++i;
            }
        }
        lua_pop(L, 1); // Stack: table, k
    }
    return colors;
}

static int screen_matchColors(lua_State *L) {
    std::string colors;

    int type = lua_type(L, 1);
    if (type == LUA_TSTRING) {
        colors = lua_tostring(L, 1);
    } else if (type == LUA_TTABLE) {
        colors = tolua_colorTable_to_colors(L, 1);
    }

    if (colors.empty()) {
        tolua_Error tolua_err = {1, 0, "string or table"};
        tolua_error(L, "#fscreen.matchColors", &tolua_err);
    }

    int globalFuzz = luaL_optint(L, 2, FUZZINESS_DEFAULT);
    bool bMatchColor = Screen::getInstance()->matchColors(colors, globalFuzz);
    lua_pushboolean(L, bMatchColor ? 1 : 0);
    return 1;
}

static int screen_findImage(lua_State *L) {
    return 0;
}

static int screen_findColor(lua_State *L) {
    Point point = Point::INVALID;

    do {
        tolua_Error tolua_err;
        if (!tolua_isusertype(L, 1, USERTYPE_RECT, LUA_TNIL, &tolua_err)) {
            tolua_error(L, "#fscreen.findColor", &tolua_err);
            break;
        }

        Rect *rect = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
        if (!rect) {
            break;
        }

        std::string colors;

        char buf[128] = {};
        int globalFuzz = FUZZINESS_DEFAULT;
        int priority = Screen::PRIORITY_DEFAULT;

        int type = lua_type(L, 2);
        if (type == LUA_TNUMBER) {
            int color = luaL_checkint(L, 2);
            snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, color); // 填写0,0时使用相对坐标体系
            colors = buf;

            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
        } else if (type == LUA_TUSERDATA) {
            if (tolua_isusertype(L, 2, USERTYPE_COLOR3F, LUA_TNIL, &tolua_err)) {
                Color3F *c3f = (Color3F *) (tolua_tousertype(L, 2, nullptr));
                if (c3f) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, c3f->toInt());
                    colors = buf;

                    globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
                }
            } else if (tolua_isusertype(L, 2, USERTYPE_COLOR3B, LUA_TNIL, &tolua_err)) {
                Color3B *c3b = (Color3B *) (tolua_tousertype(L, 2, nullptr));
                if (c3b) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, c3b->toInt());
                    colors = buf;

                    globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
                }
            } else {
                tolua_error(L, "#fscreen.findColor", &tolua_err);
            }
        } else if (type == LUA_TSTRING) {
            colors = lua_tostring(L, 2);
            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
        } else if (type == LUA_TTABLE) {
            colors = tolua_colorTable_to_colors(L, 2);
            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
        } else {
            tolua_error(L, "#fscreen.findColor", &tolua_err);
        }

        if (colors.empty()) {
            break;
        }

        Screen::POINT_LIST pointList = Screen::getInstance()->findColors(*rect, colors, globalFuzz,
                                                                         priority, 1); // 只取第一个找到的坐标
        if (pointList.empty()) {
            break;
        }

        point = pointList[0];
    } while (0);

    Point *ret = Point::createWith(point);
    tolua_pushusertype_and_takeownership(L, ret, USERTYPE_POINT);
    return 1;
}

static int screen_findColors(lua_State *L) {
    Screen::POINT_LIST pointList;

    do {
        tolua_Error tolua_err;
        if (!tolua_isusertype(L, 1, USERTYPE_RECT, LUA_TNIL, &tolua_err)) {
            tolua_error(L, "#fscreen.findColors", &tolua_err);
            break;
        }

        Rect *rect = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
        if (!rect) {
            break;
        }

        std::string colors;

        char buf[128] = {};
        int globalFuzz = FUZZINESS_DEFAULT;
        int priority = Screen::PRIORITY_DEFAULT;
        int limit = LIMIT_DEFAULT;

        int type = lua_type(L, 2);
        if (type == LUA_TNUMBER) {
            int color = luaL_checkint(L, 2);
            snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, color); // 填写0,0时使用相对坐标体系
            colors = buf;

            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
            limit = luaL_optint(L, 5, LIMIT_DEFAULT);
        } else if (type == LUA_TUSERDATA) {
            if (tolua_isusertype(L, 2, USERTYPE_COLOR3F, LUA_TNIL, &tolua_err)) {
                Color3F *c3f = (Color3F *) (tolua_tousertype(L, 2, nullptr));
                if (c3f) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, c3f->toInt());
                    colors = buf;

                    globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
                    limit = luaL_optint(L, 5, LIMIT_DEFAULT);
                }
            } else if (tolua_isusertype(L, 2, USERTYPE_COLOR3B, LUA_TNIL, &tolua_err)) {
                Color3B *c3b = (Color3B *) (tolua_tousertype(L, 2, nullptr));
                if (c3b) {
                    snprintf(buf, sizeof(buf), "%d|%d|0x%06x", 0, 0, c3b->toInt());
                    colors = buf;

                    globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
                    priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
                    limit = luaL_optint(L, 5, LIMIT_DEFAULT);
                }
            } else {
                tolua_error(L, "#fscreen.findColors", &tolua_err);
            }
        } else if (type == LUA_TSTRING) {
            colors = lua_tostring(L, 2);
            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
            limit = luaL_optint(L, 5, LIMIT_DEFAULT);
        } else if (type == LUA_TTABLE) {
            colors = tolua_colorTable_to_colors(L, 2);
            globalFuzz = luaL_optint(L, 3, FUZZINESS_DEFAULT);
            priority = luaL_optint(L, 4, Screen::PRIORITY_DEFAULT);
            limit = luaL_optint(L, 5, LIMIT_DEFAULT);
        } else {
            tolua_error(L, "#fscreen.findColors", &tolua_err);
        }

        if (colors.empty()) {
            break;
        }

        pointList = Screen::getInstance()->findColors(*rect, colors, globalFuzz, priority, limit);
    } while (0);

    int size = pointList.size();
    lua_createtable(L, size, 0);
    for (int i = 0; i < size; ++i) {
        lua_createtable(L, 2, 0);

        lua_pushinteger(L, pointList[i].x);    // Stack: table, table, x
        lua_setfield(L, -2, "x");           // Stack: table, table

        lua_pushinteger(L, pointList[i].y);    // Stack: table, table, y
        lua_setfield(L, -2, "y");           // Stack: table, table

        lua_rawseti(L, -2, i + 1);
    }

    return 1;
}

static int screen_setMockMode(lua_State *L) {
    return 0;
}

static int screen_setMockTransform(lua_State *L) {
    return 0;
}

static const luaL_Reg funcs[] = {
        {"__tostring",       screen_tostring},
        {"init",             screen_init},
        {"getSize",          screen_getSize},
        {"getDPI",           screen_getDPI},
        {"capture",          screen_capture},
        {"keep",             screen_keep},
        {"getOrientation",   screen_getOrientation},
        {"snapshot",         screen_snapshot},
        {"getRGB",           screen_getRGB},
        {"getColor",         screen_getColor},
        {"matchColor",       screen_matchColor},
        {"matchColors",      screen_matchColors},
        {"findImage",        screen_findImage},
        {"findColor",        screen_findColor},
        {"findColors",       screen_findColors},
        {"setMockMode",      screen_setMockMode},
        {"setMockTransform", screen_setMockTransform},
        {nullptr,            nullptr}
};

static int get_LANDSCAPE_RIGHT(lua_State *L) {
    lua_pushinteger(L, Screen::LANDSCAPE_RIGHT);
    return 1;
}

static int get_LANDSCAPE_LEFT(lua_State *L) {
    lua_pushinteger(L, Screen::LANDSCAPE_LEFT);
    return 1;
}

static int get_PORTRAIT(lua_State *L) {
    lua_pushinteger(L, Screen::PORTRAIT);
    return 1;
}

static int get_PRIORITY_DEFAULT(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_DEFAULT);
    return 1;
}

static int get_PRIORITY_LEFT_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_LEFT_FIRST);
    return 1;
}

static int get_PRIORITY_RIGHT_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_RIGHT_FIRST);
    return 1;
}

static int get_PRIORITY_UP_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_UP_FIRST);
    return 1;
}

static int get_PRIORITY_DOWN_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_DOWN_FIRST);
    return 1;
}

static int get_PRIORITY_HORIZONTAL_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_HORIZONTAL_FIRST);
    return 1;
}

static int get_PRIORITY_VERTICAL_FIRST(lua_State *L) {
    lua_pushinteger(L, Screen::PRIORITY_VERTICAL_FIRST);
    return 1;
}

static int get_MOCK_NONE(lua_State *L) {
    lua_pushinteger(L, Screen::MOCK_NONE);
    return 1;
}

static int get_MOCK_INPUT(lua_State *L) {
    lua_pushinteger(L, Screen::MOCK_INPUT);
    return 1;
}

static int get_MOCK_OUTPUT(lua_State *L) {
    lua_pushinteger(L, Screen::MOCK_OUTPUT);
    return 1;
}

static int get_MOCK_BOTH(lua_State *L) {
    lua_pushinteger(L, Screen::MOCK_BOTH);
    return 1;
}

LUAMOD_API int luaopen_screen(lua_State *L) {
    tolua_open(L);

    tolua_module(L, nullptr, 0);
    tolua_beginmodule(L, nullptr);
    {
        tolua_usertype(L, USERTYPE_SCREEN);
        tolua_cclass(L, CLASSNAME_SCREEN, USERTYPE_SCREEN, "", nullptr);

        tolua_beginmodule(L, CLASSNAME_SCREEN);
        {
            toluahelper_setfunc(L, funcs);

            TOLUAHELPER_CLASS_CONST_VARIABLE(L, LANDSCAPE_RIGHT);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, LANDSCAPE_LEFT);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PORTRAIT);

            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_DEFAULT);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_LEFT_FIRST);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_RIGHT_FIRST);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_UP_FIRST);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_DOWN_FIRST);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_HORIZONTAL_FIRST);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, PRIORITY_VERTICAL_FIRST);

            TOLUAHELPER_CLASS_CONST_VARIABLE(L, MOCK_NONE);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, MOCK_INPUT);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, MOCK_OUTPUT);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, MOCK_BOTH);
        }
        tolua_endmodule(L);
    }
    tolua_endmodule(L);
    return 0;
}